package com.coincalf.wallet.blockchain;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.coincalf.wallet.blockchain.bean.BaseUtxo;
import com.coincalf.wallet.blockchain.bean.SimpleUtxo;
import com.coincalf.wallet.blockchain.bean.TransInfo;
import com.coincalf.wallet.blockchain.client.UsdtClientSingleton;
import com.coincalf.wallet.blockchain.util.AESUtil;
import com.coincalf.wallet.blockchain.util.HttpUtil;
import com.coincalf.wallet.blockchain.util.RpcHttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Component;
import wf.bitcoin.javabitcoindrpcclient.BitcoinJSONRPCClient;
import wf.bitcoin.javabitcoindrpcclient.BitcoindRpcClient;
import wf.bitcoin.javabitcoindrpcclient.GenericRpcException;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Slf4j
@Component
public class UsdtBlockChainService {

    /**
     * 获取USDT地址余额
     *
     * @param address 钱包地址
     * @return 地址余额
     */
    public BigDecimal getBalance(String address) {
        try {
            BitcoinJSONRPCClient client = UsdtClientSingleton.getUsdtClient();
            LinkedHashMap query = (LinkedHashMap) client.query(BlockConstant.GET_BALANCE, address, BlockConstant.PROPERTY_ID);
            String balance = (String) query.get(BlockConstant.BALANCE);
            return new BigDecimal(balance);
        } catch (Exception e) {
            log.error("获取usdt余额异常 e={}", e);
            return null;
        }
    }

    /**
     * 构建usdt交易
     * ps:泰达币交易需要地址有btc余额
     *
     * @param bean
     * @return
     */
    public TransInfo trans(TransInfo bean) {
        TransInfo transInfo = new TransInfo();
        log.info("USDT交易开始>>>>>>>>{}", JSON.toJSONString(bean));
        try {
            String prk = AESUtil.decrypt(bean.getPrivateKey(), BlockConstant.AES_KEY);
            long amount = bean.getAmount().multiply(BlockConstant.SATOSHIS).longValue();
            long fee = bean.getFee().multiply(BlockConstant.SATOSHIS).longValue();
            String txHash = offlineSignature(bean.getFromAddress(), prk, bean.getToAddress(), amount, fee);
            if (StringUtils.isNotBlank(txHash)) {
                transInfo.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
                transInfo.setAmount(bean.getAmount());
                transInfo.setTxHash(txHash);
            } else {
                log.info("USDT交易失败");
                transInfo.setStatus(TxStateEnum.FAIL.getStatus());
            }
        } catch (BlockException e) {
            transInfo.setStatus(TxStateEnum.FAIL.getStatus());
            log.error("USDT交易失败 >>>>>>>> e={}", e);
        }
        return transInfo;
    }

    /**
     * 查询USDT交易详情
     *
     * @param txHash 交易Hash
     * @return
     */
    public TransInfo query(String txHash) {
        TransInfo bean = new TransInfo();
        bean.setTxHash(txHash);
        try {
            BitcoinJSONRPCClient client = UsdtClientSingleton.getUsdtClient();
            Map<String, Object> query = (Map<String, Object>) client.query(BlockConstant.GET_TRANSACTION, txHash);
            bean.setFee((BigDecimal) query.get(BlockConstant.FEE));
            Object blockNumber = query.get(BlockConstant.BLOCK_HASH);
            if (blockNumber == null) {
                log.info("USDT交易txHash={} 未同步", txHash);
                bean.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
            } else {
                bean.setStatus(TxStateEnum.SUCCESS.getStatus());
                bean.setBlockNumber((long) blockNumber);
                bean.setConfirmations((long) query.get(BlockConstant.CONFIRMATIONS));
            }
        } catch (Exception e) {
            bean.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
            log.error("USDT交易查询异常 txHash={} e={}", txHash, e);
        }
        return bean;
    }

    /************************************USDT 离线签名 START************************************/

    /**
     * 获取私钥
     */
    private String getPrivateKey(String address) {
        try {
            String privateKey = UsdtClientSingleton.getUsdtClient().dumpPrivKey(address);
            log.info("导出私钥成功 -> address={}, privateKey={}", address, privateKey);
            return privateKey;
        } catch (Exception e) {
            log.error("导出私钥失败 -> address={}", address);
        }
        return null;
    }

    /**
     * usdt 离线签名交易
     *
     * @param fromAddress 转账方
     * @param toAddress   收款方
     * @param amount      转账金额 单位：USDT
     * @param fee         矿工费 单位：聪
     * @return
     */
    private String OfflineSignTx(String fromAddress, String toAddress, long amount, long fee) throws BlockException {
        List<UTXO> utxos;
        utxos = getUnspents(fromAddress);
        // TODO: 2019/6/28 设置手续费 暂时写死0.00015
        //Long fee = getOmniFee(utxos); //此处是通过调用第三方API获取当前合适费率 设置手续费
        NetworkParameters networkParameters = MainNetParams.get();
        Transaction tran = new Transaction(networkParameters);
        if (utxos.size() == 0) {
            throw new BlockException("utxo为空 BTC INSUFFICIENT FEE");
        }
        //比特币的限制最小转账金额 -> 0.00000546 BTC
        long miniBtc = 546L;
        //构建有效荷载
        tran.addOutput(Coin.valueOf(miniBtc), Address.fromString(networkParameters, toAddress));

        //构建usdt的输出脚本
        String usdtHex = BlockConstant.HEX_SCRIPT + String.format("%016x", BlockConstant.PROPERTY_ID) + String.format("%016x", amount);
        tran.addOutput(Coin.valueOf(0L), new Script(Utils.HEX.decode(usdtHex)));

        long changeAmount;
        long utxoAmount = 0L;
        List<UTXO> needUtxo = new ArrayList<>();
        //过滤掉多的uxto
        for (UTXO utxo : utxos) {
            if (utxoAmount > (fee + miniBtc)) {
                break;
            } else {
                needUtxo.add(utxo);
                utxoAmount += utxo.getValue().value;
            }
        }
        //找零
        changeAmount = utxoAmount - (fee + miniBtc);
        if (changeAmount < 0) {
            throw new BlockException("utxo余额不足 BTC INSUFFICIENT FEE");
        }
        if (changeAmount > 0) {
            tran.addOutput(Coin.valueOf(changeAmount), Address.fromString(networkParameters, fromAddress));
        }
        //添加未签名的交易输入，也就是utxo
        for (UTXO utxo : needUtxo) {
            tran.addInput(utxo.getHash(), utxo.getIndex(), utxo.getScript()).setSequenceNumber(TransactionInput.NO_SEQUENCE - 2);
        }
        //对其签名
        int size = needUtxo.size();
        for (int i = 0; i < size; i++) {
            //这里获取地址
            String address = needUtxo.get(i).getAddress();
            String privateKey = getPrivateKey(address);
            if (privateKey == null) {
                return null;
            }
            ECKey ecKey = DumpedPrivateKey.fromBase58(networkParameters, privateKey).getKey();
            TransactionInput transactionInput = tran.getInput(i);
            Script scriptPubKey = ScriptBuilder.createOutputScript(Address.fromString(networkParameters, address));
            Sha256Hash hash = tran.hashForSignature(i, scriptPubKey, Transaction.SigHash.ALL, false);
            ECKey.ECDSASignature ecSig = ecKey.sign(hash);
            TransactionSignature txSig = new TransactionSignature(ecSig, Transaction.SigHash.ALL, false);
            transactionInput.setScriptSig(ScriptBuilder.createInputScript(txSig, ecKey));
        }
        //签名之后的原始交易，进行广播
        String signedHex = Hex.toHexString(tran.bitcoinSerialize());
        log.info("签名之后的原始交易:{}", signedHex);
        //交易hash
        String txHash = Hex.toHexString(Utils.reverseBytes(Sha256Hash.hash(Sha256Hash.hash(tran.bitcoinSerialize()))));
        log.info("fee:{}, utxoAmount:{}, changeAmount:{}, txHash={}", fee, utxoAmount, changeAmount, txHash);
        String result = UsdtClientSingleton.getUsdtClient().sendRawTransaction(signedHex);
        if (result != null) {
            log.info("USDT交易成功， result={}", result);
            return result;
        }
        return null;
    }

    /**
     * 获取矿工费用
     */
    //    private Long getOmniFee(List<UTXO> utxos) {
    //        Long miniBtc = 546L;
    //        Long feeRate = getFeeRate();
    //        Long utxoAmount = 0L;
    //        Long fee = 0L;
    //        Long utxoSize = 0L;
    //        for (UTXO output : utxos) {
    //            utxoSize++;
    //            if (utxoAmount > (fee + miniBtc)) {
    //                break;
    //            } else {
    //                utxoAmount += output.getValue().value;
    //                fee = (utxoSize * 148 + 34 * 2 + 10) * feeRate;
    //            }
    //        }
    //        return fee;
    //    }

    /**
     * 获取btc费率 第三方节点获取
     * 三种(从高到低，从快到慢)：fastestFee  halfHourFee   hourFee
     */
    //    private Long getFeeRate() {
    //        try {
    //            String httpGet1 = HttpUtil.get(PropertiesUtil.getProperty("usdt.fee.rate.url", "https://bitcoinfees.earn.com/api/v1/fees/recommended"));
    //            Map map = JSON.parseObject(httpGet1, Map.class);
    //            //配置文件读取费率
    //            return Long.valueOf(map.get(PropertiesUtil.getProperty("usdt.rate", "fastestFee")).toString());
    //        } catch (Exception e) {
    //            e.printStackTrace();
    //            return 0L;
    //        }
    //    }

    /**
     * 获取未花费的交易输出
     *
     * @param address USDT地址
     * @return UTXOS
     */
    public List<UTXO> getUnspents(String address) throws BlockException {
        List<UTXO> utxos = new ArrayList<>();
        List<BitcoindRpcClient.Unspent> unspentOutPuts = UsdtClientSingleton.getUsdtClient().listUnspent(0, 99999999, address);
        if (unspentOutPuts == null || unspentOutPuts.size() == 0) {
            throw new BlockException("utxo为空 BTC INSUFFICIENT FEE");
        }
        for (BitcoindRpcClient.Unspent unspent : unspentOutPuts) {
            long vOut = unspent.vout();
            String txId = unspent.txid();
            String addressStr = unspent.address();
            String script = unspent.scriptPubKey();
            BigDecimal amount = unspent.amount();
            amount = amount.multiply(new BigDecimal("100000000"));
            UTXO utxo = new UTXO(Sha256Hash.wrap(txId), vOut, Coin.valueOf(amount.longValue()),
                    0, false, new Script(Hex.decode(script)), addressStr
            );
            utxos.add(utxo);
        }
        return utxos;
    }
    /************************************USDT 离线签名 END************************************/

    /************************************USDT OMNI_FUNDED_SEND START************************************/

    /**
     * USDT原始交易构建
     *
     * @param from   转账地址
     * @param to     收款地址
     * @param amount 转账金额 单位:USDT
     * @param fee    手续费 单位:BTC
     * @return 交易哈希
     */
    public String rawUsdtTx(String from, String to, BigDecimal amount, BigDecimal fee) {
        BitcoinJSONRPCClient client = UsdtClientSingleton.getUsdtClient();
        List<BitcoindRpcClient.Unspent> feeUtxos = client.listUnspent(0, 99999999, from);
        if (feeUtxos == null || feeUtxos.size() == 0) {
            throw new BlockException("TRAINS ADDRESS UTXO INSUFFICIENT");
        }
        List<BitcoindRpcClient.Unspent> needUtxo = new ArrayList<>();
        BigDecimal sum = BigDecimal.ZERO;
        for (BitcoindRpcClient.Unspent utxo : feeUtxos) {
            sum = sum.add((utxo.amount()));
            needUtxo.add(utxo);
        }
        //fee此处添加
        if (sum.compareTo(BigDecimal.valueOf(0.00010546D)) < 0) {
            throw new BlockException("TRAINS ADDRESS UTXO INSUFFICIENT");
        }
        try {
            String simpleSend = (String) client.query(BlockConstant.OMNI_CREATEPAYLOAD_SIMPLESEND, BlockConstant.PROPERTY_ID, amount.toString());
            log.info("simpleSend={}", simpleSend);
            //交易输入
            ArrayList<BaseUtxo> inputs = new ArrayList<>();
            for (BitcoindRpcClient.Unspent u : needUtxo) {
                inputs.add(new BaseUtxo(u.txid(), u.vout()));
            }
            //交易输出
            HashMap<String, Double> outputs = new HashMap<>();
            String baseRawTx = (String) client.query(BlockConstant.CREATE_RAW_TRANSACTION, JSONObject.toJSON(inputs), outputs);
            String rawTxOpreturn = (String) client.query(BlockConstant.OMNI_CREATERAWTX_OPRETURN, baseRawTx, simpleSend);
            String rawTxReference = (String) client.query(BlockConstant.OMNI_CREATERAWTX_REFERENCE, rawTxOpreturn, to);
            //6.指定矿工费并附加变更输出（根据需要）
            ArrayList<SimpleUtxo> list = new ArrayList<>();
            for (BitcoindRpcClient.Unspent u : needUtxo) {
                list.add(new SimpleUtxo(u.txid(), u.vout(), u.scriptPubKey(), u.amount().doubleValue()));
            }
            String rawTxChange = (String) client.query(BlockConstant.OMNI_CREATE_RAWTX_CHANGE, rawTxReference, JSONObject.toJSON(list), from, fee);
            //7.签署交易
            String signHex = client.signRawTransaction(rawTxChange);
            //8.广播交易
            String txHash = client.sendRawTransaction(signHex);
            log.info("USDT 广播交易成功 txHash={}", txHash);
            return txHash;
        } catch (GenericRpcException e) {
            log.error("USDT OMNI FUNDED SEND ERROR e={}", e.getMessage());
        }
        throw new BlockException("USDT IO EXCEPTION");
    }

    /**
     * usdt 离线签名交易
     *
     * @param fromAddress 转账方
     * @param fromPrk     转账方私钥
     * @param toAddress   收款方
     * @param amount      转账金额 单位:聪
     * @param fee         手续费单位:聪
     * @return
     */
    private String offlineSignature(String fromAddress, String fromPrk, String toAddress, long amount, long fee) {
        List<UTXO> utxos = getUnspents(fromAddress);
        //Long fee = getOmniFee(utxos); //此处是通过调用第三方API获取当前合适费率 设置手续费
        NetworkParameters networkParameters = MainNetParams.get();
        Transaction tran = new Transaction(networkParameters);
        if (utxos.size() == 0) {
            throw new BlockException("utxo为空 BTC INSUFFICIENT FEE");
        }
        //比特币的限制最小转账金额 -> 0.00000546 BTC
        long miniBtc = 546L;
        tran.addOutput(Coin.valueOf(miniBtc), Address.fromString(networkParameters, toAddress));

        //构建usdt的输出脚本
        String usdtHex = BlockConstant.HEX_SCRIPT + String.format(BlockConstant.STRING_FORMAT, BlockConstant.PROPERTY_ID) + String.format(BlockConstant.STRING_FORMAT, amount);
        tran.addOutput(Coin.valueOf(0L), new Script(Utils.HEX.decode(usdtHex)));
        long changeAmount;
        long utxoAmount = 0L;
        List<UTXO> needUtxo = new ArrayList<>();
        //过滤掉多的uxto
        for (UTXO utxo : utxos) {
            if (utxoAmount > (fee + miniBtc)) {
                break;
            } else {
                needUtxo.add(utxo);
                utxoAmount += utxo.getValue().value;
            }
        }
        //找零
        changeAmount = utxoAmount - (fee + miniBtc);
        if (changeAmount < 0) {
            throw new BlockException("utxo余额不足 BTC INSUFFICIENT FEE");
        }
        if (changeAmount > 0) {
            tran.addOutput(Coin.valueOf(changeAmount), Address.fromString(networkParameters, fromAddress));
        }
        //添加未签名的交易输入，也就是utxo
        for (UTXO utxo : needUtxo) {
            tran.addInput(utxo.getHash(), utxo.getIndex(), utxo.getScript()).setSequenceNumber(TransactionInput.NO_SEQUENCE - 2);
        }
        //对其签名
        int size = needUtxo.size();
        for (int i = 0; i < size; i++) {
            //这里获取地址
            String address = needUtxo.get(i).getAddress();
            //            String privateKey = getPrivateKey(address);
            ECKey ecKey = DumpedPrivateKey.fromBase58(networkParameters, fromPrk).getKey();
            TransactionInput transactionInput = tran.getInput(i);
            Script scriptPubKey = ScriptBuilder.createOutputScript(Address.fromString(networkParameters, address));
            Sha256Hash hash = tran.hashForSignature(i, scriptPubKey, Transaction.SigHash.ALL, false);
            ECKey.ECDSASignature ecSig = ecKey.sign(hash);
            TransactionSignature txSig = new TransactionSignature(ecSig, Transaction.SigHash.ALL, false);
            transactionInput.setScriptSig(ScriptBuilder.createInputScript(txSig, ecKey));
        }
        //签名之后的原始交易，进行广播
        String signedHex = Hex.toHexString(tran.bitcoinSerialize());
        log.info("签名之后的原始交易:{}", signedHex);
        //交易hash
        String txHash = Hex.toHexString(Utils.reverseBytes(Sha256Hash.hash(Sha256Hash.hash(tran.bitcoinSerialize()))));
        log.info("fee:{}, utxoAmount:{}, changeAmount:{}, txHash={}", fee, utxoAmount, changeAmount, txHash);
        try {
            return UsdtClientSingleton.getUsdtClient().sendRawTransaction(signedHex);
        } catch (GenericRpcException e) {
            log.error("USDT交易异常 e={}", e);
            throw new BlockException(e.getMessage());
        }
    }

    private static RpcHttpUtil http = new RpcHttpUtil();

    public static String tx(String from, String to, String feeAddress, BigDecimal amount) {
        log.info("USDT_OMNI归集交易开始 from={} to={} feeAddress={} amount={}", from, to, feeAddress, amount);
        BitcoinJSONRPCClient client = UsdtClientSingleton.getUsdtClient();
        /**
         * 1.UTXO获取
         * 转账地址from必须至少附带一个BTC的UTXO 所有附带的UTXO会被找零到feeAddress
         * from UTXO获取原则 -> 选择一个等于546聪的UTXO
         */
        List<BitcoindRpcClient.Unspent> fromUnspentOutPuts = client.listUnspent(0, 99999999, from);
        if (fromUnspentOutPuts == null || fromUnspentOutPuts.size() == 0) {
            throw new BlockException("FROM MUST HAVE UTXO");
        }
        //找零 最小的UTXO 其实也就是546
        BitcoindRpcClient.Unspent fromUtxos = fromUnspentOutPuts.get(0);
        for (BitcoindRpcClient.Unspent utxo : fromUnspentOutPuts) {
            if (fromUtxos.amount().compareTo(utxo.amount()) > 0) {
                fromUtxos = utxo;
            }
        }
        //feeAddress 用于支付转账手续费
        List<BitcoindRpcClient.Unspent> feeUtxos = client.listUnspent(0, 99999999, feeAddress);
        if (feeUtxos == null || feeUtxos.size() == 0) {
            throw new BlockException("FEE ADDRESS UTXO INSUFFICIENT");
        }

        List<BitcoindRpcClient.Unspent> needUtxo = new ArrayList<>();
        BigDecimal sum = BigDecimal.ZERO;
        long feeL = (2 * 148 + 34 * 2 + 10) * getFeeRate();
        BigDecimal fee = BigDecimal.valueOf(feeL).divide(BlockConstant.SATOSHIS, 8, RoundingMode.DOWN);

        for (BitcoindRpcClient.Unspent utxo : feeUtxos) {
            if (sum.compareTo(fee.add(fee)) >= 0) {
                break;
            } else {
                sum = sum.add(utxo.amount());
                needUtxo.add(utxo);
            }
        }

        try {
            /**
             * 2.构造发送代币类型和代币数量数据
             */
            //String simpleSend = (String) client.query(BlockConstant.OMNI_CREATEPAYLOAD_SIMPLESEND, BlockConstant.PROPERTY_ID, amount.toString());
            String simpleSend = http.engine(BlockConstant.OMNI_CREATEPAYLOAD_SIMPLESEND, String.class, BlockConstant.PROPERTY_ID, amount.toString());
            /**
             * 3.构造交易基本数据
             * 添加顺序很重要 不能错 必须先添加from 然后再feeAddress
             */
            //交易输入
            ArrayList<BaseUtxo> inputs = new ArrayList<>();
            inputs.add(new BaseUtxo(fromUtxos.txid(), fromUtxos.vout()));
            for (BitcoindRpcClient.Unspent u : needUtxo) {
                inputs.add(new BaseUtxo(u.txid(), u.vout()));
            }
            //空的交易输出
            HashMap<String, Double> outputs = new HashMap<>();
            //String baseRawTx = (String) client.query(BlockConstant.CREATE_RAW_TRANSACTION, JSONObject.toJSON(inputs), outputs);
            String baseRawTx = http.engine(BlockConstant.CREATE_RAW_TRANSACTION, String.class, inputs, outputs);
            /**
             * 4.在交易数据中加上omni代币数据,从第2步获取有效负载，从第3步获取基本交易
             */
            //String rawTxOpreturn = (String) client.query(BlockConstant.OMNI_CREATERAWTX_OPRETURN, String.class, baseRawTx, simpleSend);
            String rawTxOpreturn = http.engine(BlockConstant.OMNI_CREATERAWTX_OPRETURN, String.class, baseRawTx, simpleSend);
            /**
             * 5.在交易数据上加上接收地址，从步骤4获取扩展事务，并将参考输出添加到"接收地址"，它将成为令牌的接收者
             * 同时添加一个from作为接收者，返回546聪BTC
             */
            //String rawTxReference = (String) client.query(BlockConstant.OMNI_CREATERAWTX_REFERENCE, String.class, rawTxOpreturn, to);
            //String result = (String) client.query(BlockConstant.OMNI_CREATERAWTX_REFERENCE, String.class, rawTxReference, from);

            String rawTxReference = http.engine(BlockConstant.OMNI_CREATERAWTX_REFERENCE, String.class, rawTxOpreturn, to);
            String result = http.engine(BlockConstant.OMNI_CREATERAWTX_REFERENCE, String.class, rawTxReference, from);
            //6.指定矿工费并附加变更输出（根据需要）
            ArrayList<SimpleUtxo> list = new ArrayList<>();
            list.add(new SimpleUtxo(fromUtxos.txid(), fromUtxos.vout(), fromUtxos.scriptPubKey(), fromUtxos.amount().doubleValue()));
            for (BitcoindRpcClient.Unspent u : needUtxo) {
                list.add(new SimpleUtxo(u.txid(), u.vout(), u.scriptPubKey(), u.amount().doubleValue()));
            }
            //String rawTxChange = (String) client.query(BlockConstant.OMNI_CREATE_RAWTX_CHANGE, String.class, result, JSONObject.toJSON(list), feeAddress, fee);
            String rawTxChange = http.engine(BlockConstant.OMNI_CREATE_RAWTX_CHANGE, String.class, result, list, feeAddress, fee);
            //7.签名交易
            String signHex = client.signRawTransaction(rawTxChange);
            log.info("USDT 签名交易成功 signHex={}", signHex);
            //8.广播交易
            String txHash = client.sendRawTransaction(signHex);
            log.info("USDT 广播交易成功 txHash={}", txHash);
            return txHash;
        } catch (GenericRpcException e) {
            log.error("USDT OMNI FUNDED SEND ERROR e={}", e.getMessage());
        }
        throw new BlockException("USDT IO EXCEPTION");
    }

    /**
     * USDT转账，指定手续费地址 详细信息请参考 -> https://gist.github.com/dexX7/352670c990ebf9ea31d6346a1519eb52
     *
     * @param from       转账地址
     * @param to         收款地址
     * @param feeAddress 支付手续费地址
     * @param amount     转账金额
     * @param fee        手续费 单位：BTC
     * @return 交易哈希
     */
    public static String tx(String from, String to, String feeAddress, BigDecimal amount, BigDecimal fee) {
        BitcoinJSONRPCClient client = UsdtClientSingleton.getUsdtClient();
        /**
         * 1.UTXO获取
         * 转账地址from必须至少附带一个BTC的UTXO 所有附带的UTXO会被找零到feeAddress
         * from UTXO获取原则 -> 选择一个等于546聪的UTXO
         */
        List<BitcoindRpcClient.Unspent> fromUnspentOutPuts = client.listUnspent(0, 99999999, from);
        if (fromUnspentOutPuts == null || fromUnspentOutPuts.size() == 0) {
            throw new BlockException("FROM MUST HAVE UTXO");
        }
        //找零 最小的UTXO 其实也就是546
        BitcoindRpcClient.Unspent fromUtxos = fromUnspentOutPuts.get(0);
        for (BitcoindRpcClient.Unspent utxo : fromUnspentOutPuts) {
            if (fromUtxos.amount().compareTo(utxo.amount()) > 0) {
                fromUtxos = utxo;
            }
        }
        //feeAddress 用于支付转账手续费
        List<BitcoindRpcClient.Unspent> feeUtxos = client.listUnspent(0, 99999999, feeAddress);
        if (feeUtxos == null || feeUtxos.size() == 0) {
            throw new BlockException("FEE ADDRESS UTXO INSUFFICIENT");
        }
        List<BitcoindRpcClient.Unspent> needUtxo = new ArrayList<>();
        BigDecimal sum = BigDecimal.ZERO;
        for (BitcoindRpcClient.Unspent utxo : feeUtxos) {
            if (sum.compareTo(fee.add(fee)) >= 0) {
                break;
            } else {
                sum = sum.add(utxo.amount());
                needUtxo.add(utxo);
            }
        }
        /**
         * 判断feeAddress余额是否足够
         * 注意：如果是普通USDT转账，此处需要判断两次 第一次为用户USDT转账手续费，第二次为扣除用户USDT到公司账户手续费
         *      所以，为了兼容普通转账，取两次FEE
         */
        if (sum.compareTo(fee.add(fee)) < 0) {
            throw new BlockException("FEE ADDRESS UTXO INSUFFICIENT");
        }
        try {
            /**
             * 2.构造发送代币类型和代币数量数据
             */
            String simpleSend = (String) client.query(BlockConstant.OMNI_CREATEPAYLOAD_SIMPLESEND, BlockConstant.PROPERTY_ID, amount.toString());
            /**
             * 3.构造交易基本数据
             * 添加顺序很重要 不能错 必须先添加from 然后再feeAddress
             */
            //交易输入
            ArrayList<BaseUtxo> inputs = new ArrayList<>();
            inputs.add(new BaseUtxo(fromUtxos.txid(), fromUtxos.vout()));
            for (BitcoindRpcClient.Unspent u : needUtxo) {
                inputs.add(new BaseUtxo(u.txid(), u.vout()));
            }
            //空的交易输出
            HashMap<String, Double> outputs = new HashMap<>();
            String baseRawTx = (String) client.query(BlockConstant.CREATE_RAW_TRANSACTION, JSONObject.toJSON(inputs), outputs);
            /**
             * 4.在交易数据中加上omni代币数据,从第2步获取有效负载，从第3步获取基本交易
             */
            String rawTxOpreturn = (String) client.query(BlockConstant.OMNI_CREATERAWTX_OPRETURN, String.class, baseRawTx, simpleSend);
            /**
             * 5.在交易数据上加上接收地址，从步骤4获取扩展事务，并将参考输出添加到"接收地址"，它将成为令牌的接收者
             * 同时添加一个from作为接收者，返回546聪BTC
             */
            String rawTxReference = (String) client.query(BlockConstant.OMNI_CREATERAWTX_REFERENCE, String.class, rawTxOpreturn, to);
            String result = (String) client.query(BlockConstant.OMNI_CREATERAWTX_REFERENCE, String.class, rawTxReference, from);
            //6.指定矿工费并附加变更输出（根据需要）
            ArrayList<SimpleUtxo> list = new ArrayList<>();
            list.add(new SimpleUtxo(fromUtxos.txid(), fromUtxos.vout(), fromUtxos.scriptPubKey(), fromUtxos.amount().doubleValue()));
            for (BitcoindRpcClient.Unspent u : needUtxo) {
                list.add(new SimpleUtxo(u.txid(), u.vout(), u.scriptPubKey(), u.amount().doubleValue()));
            }
            String rawTxChange = (String) client.query(BlockConstant.OMNI_CREATE_RAWTX_CHANGE, String.class, result, JSONObject.toJSON(list), feeAddress, fee);
            //7.签名交易
            String signHex = client.signRawTransaction(rawTxChange);
            log.info("USDT 签名交易成功 signHex={}", signHex);
            //8.广播交易
            String txHash = client.sendRawTransaction(signHex);
            log.info("USDT 广播交易成功 txHash={}", txHash);
            return txHash;
        } catch (GenericRpcException e) {
            log.error("USDT OMNI FUNDED SEND ERROR e={}", e.getMessage());
        }
        throw new BlockException("USDT IO EXCEPTION");
    }
    /************************************USDT OMNI_FUNDED_SEND END************************************/

    /**
     * 根据区块高度查询区块所有交易hash
     *
     * @param blockHeight 区块高度
     * @return 区块all txHash
     */
    public static List<String> getTxHashesByBlockHeight(long blockHeight) {
        return (List<String>) UsdtClientSingleton.getUsdtClient().query(BlockConstant.LIST_BLOCK_TRANSACTIONS, blockHeight);
    }

    /**
     * 根据交易hash获取交易详情
     *
     * @param txHash 交易哈希
     * @return 交易详情
     */
    public static Map<String, Object> getTransaction(String txHash) {
        return (Map<String, Object>) UsdtClientSingleton.getUsdtClient().query(BlockConstant.GET_TRANSACTION, txHash);
    }

    /**
     * 获取矿工费用 单位：聪
     */
    private static Long getOmniFee(List<UTXO> utxos) {
        Long miniBtc = 546L;
        Long feeRate = getFeeRate();
        Long utxoAmount = 0L;
        Long fee = 0L;
        Long utxoSize = 0L;
        for (UTXO output : utxos) {
            utxoSize++;
            if (utxoAmount > (fee + miniBtc)) {
                break;
            } else {
                utxoAmount += output.getValue().value;
                fee = (utxoSize * 148 + 34 * 2 + 10) * feeRate;
            }
        }
        return fee;
    }

    /**
     * 获取btc费率 第三方节点获取
     * 三种(从高到低，从快到慢)：fastestFee  halfHourFee   hourFee
     */
    public static Long getFeeRate() {
        try {
            String httpGet1 = HttpUtil.doGet("https://bitcoinfees.earn.com/api/v1/fees/recommended");
            Map map = JSON.parseObject(httpGet1, Map.class);
            return Long.parseLong(map.get("fastestFee").toString());
        } catch (Exception e) {
            log.error("调用第三方接口获取BCT最佳手续费率失败 e={}", e);
            throw new BlockException("调用第三方接口获取BCT最佳手续费率失败");
        }
    }

    /*private static Long getFeeRate() {
        String btcFeeRateKey = "btc.fee.rate";
        String fee = redisTemplate.opsForValue().get(btcFeeRateKey);
        if (StringUtils.isNotBlank(fee)) {
            return Long.parseLong(fee);
        }
        try {
            String httpGet1 = HttpUtil.doGet("https://bitcoinfees.earn.com/api/v1/fees/recommended");
            Map map = JSON.parseObject(httpGet1, Map.class);

            fee = map.get("fastestFee").toString();
            redisTemplate.opsForValue().set(btcFeeRateKey, fee, 15L, TimeUnit.SECONDS);
            return Long.parseLong(fee);
        } catch (Exception e) {
            log.error("调用第三方接口获取BCT最佳手续费率失败 e={}", e);
            throw new ApiException("调用第三方接口获取BCT最佳手续费率失败");
        }
    }*/

}

